
#Rem
	==== License ====
	The contents of this file are subject To the Mozilla Public License
	Version 1.1 (the "License"); you may Not use this file except in
	compliance with the License. You may obtain a copy of the License at
	http://www.mozilla.org/MPL/
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express Or implied. See the
	License For the specific language governing rights And limitations
	under the License.
	
	The Initial Developer of the Original Code is Nicholas Grant.
	
	Contributor(s): Eric Rodriguez.
	
	Alternatively, the contents of this file may be used under the terms
	of the MIT license, in which Case the
	provisions of MIT License are applicable instead of those
	above. If you wish To allow use of your version of this file only
	under the terms of the MIT License And Not To allow others To use
	your version of this file under the MPL, indicate your decision by
	deleting the provisions above And replace them with the notice And
	other provisions required by the MIT License. If you do Not delete
	the provisions above, a recipient may use your version of this file
	under either the MPL Or the MIT License.
#End

' This is one insanely annoying part of Monkey, that clearly has to be fixed.
' It doesn't seem to do multiple passes to check if a function was defined or not
' So I'm forced to stick every function into one single file

' #################### arrays ####################

Const COUNT_NORMAL:Int = 0
Const COUNT_RECURSIVE:Int = 1

#Rem
	Summary: Counts all elements in a string array, or properties in an string.
	Like the real php function, null values are ignored in the count
	
	TODO:	Finish this function with recursive ability
	Author:	Nicholas Grant
	@updatedby	
	@return	int
#End
Function count:Int( variable:String[], mode:Int = 0 )
	Local rturn:Int = 0
	If ( Not mode )
		For Local cnt:Int=0 To variable.Length-1
			If ( Not empty( variable[cnt] ) )
				rturn += 1
			End
		Next
	Else
		
	End
	Return rturn
End

Function count:Int( variable:Int[], mode:Int = 0 )
	Local rturn:Int = 0
	If ( Not mode )
		For Local cnt:Int=0 To variable.Length-1
			If ( Not empty( variable[cnt] ) )
				rturn += 1
			End
		Next
	Else
		
	End
	Return rturn
End

Function count:Int( variable:Float[], mode:Int = 0 )
	Local rturn:Int = 0
	If ( Not mode )
		For Local cnt:Int=0 To variable.Length-1
			If ( Not empty( variable[cnt] ) )
				rturn += 1
			End
		Next
	Else
		
	End
	Return rturn
End


' #################### bcmath ####################

' #################### math ####################


#Rem
	Summary: Convert a number between arbitrary bases
	Returns a String containing number represented in base tobase. The base in which
	number is given is specified in frombase. Both frombase And tobase have To be
	between 2 And 36, inclusive. Digits in numbers with a base higher than 10 will
	be represented with the letters a-z, with a meaning 10, b meaning 11 And z meaning 35. 
	
	TODO:	Optimize
	Author:	Nicholas Grant
	@updatedby	
	@Return	String
#End
Function base_convert:String( number:String, frombase:Int, tobase:Int )
	If ( frombase > 1 And frombase < 37 And tobase > 1 And tobase < 37 )
		Local charlist$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		Local inputArray:Int[] = New Int[number.Length]
		Local outputStack:StringStack = New StringStack
		Local givenLength% = 0	' Values are recalculated, this is to prevent that
		Local output$ = ""
		Local outVal% = 0
		Local calcVal% = 0
		Local calcMod% = 0
		Local baseMul% = 1
		Local cnt% = 0
		
		number = number.ToUpper()
		
		' So as a quick explanation of what's going on
		' Step one, push the individual values in their decimal equivalent form to the top of the stack
		givenLength = number.Length - 1
		For cnt=0 To givenLength
			inputArray[cnt] = charlist.Find( number[cnt..cnt+1] )
		Next
		
		' Step two, starting from the rightmost value at 1, start to multiply the value that the
		' decimal value is then multiplied by to get the decimal equivalent of whatever base
		' the inputted number is.
		For cnt=inputArray.Length() - 1 To 0 Step - 1
			If ( cnt < inputArray.Length() - 1 )
				baseMul *= frombase
			End
			calcVal += inputArray[cnt] * baseMul
		Next
		
		
		' Step three, the math formula that finds the values in decimal form
		While ( calcVal <> 0 )
			calcMod = calcVal Mod tobase
			outputStack.Push( calcMod )
			calcVal -= calcMod
			calcVal /= tobase
		Wend
		
		' Step four, pop the value from the stack to get the slice location in the charlist string
		givenLength = outputStack.Length()
		For cnt = 1 To givenLength
			outVal = Int(String(outputStack.Pop()))
			output += charlist[outVal..outVal+1]
		Next
		
		Return output
	End
	Return ""
End


#Rem
	Summary: Returns the decimal equivalent of the binary number represented by the binary_string argument.
	
	Author:	Nicholas Grant
	@updatedby	
	@return	string
#End
Function bindec:Int( binary_string:String )
	Return Int( base_convert( binary_string, 2, 10 ) )
End

' Alias
Function bin2dec:Int( binary_string:String )
	Return Int( base_convert( binary_string, 2, 10 ) )
End


#Rem
	Summary: Returns a string containing a binary representation of the given number argument.
	
	Author:	Nicholas Grant
	@updatedby	
	@return	string
#End
Function decbin:String( number:Int )
	Return base_convert( String( number ), 10, 2 )
End

Function dec2bin:String( number:Int )
	Return base_convert( String( number), 10, 2 )
End

'Overloading
Function decbin:String( number:String )
	Return base_convert( number, 10, 2 )
End

Function dec2bin:String( number:String )
	Return base_convert( number, 10, 2 )
End


#Rem
	Summary: Returns a string containing a hexadecimal representation of the given number argument.
	
	TODO:		Write function from scratch. This doesn't fully simulate the
			php function. It leaves trailing 0s to the left.
	Author:	Nicholas Grant
	@updatedby	
	@return	string
#End
Function dechex:String( number:Int )
	Return base_convert( String( number ), 10, 16 )
End

' Alias-like Function
Function dec2hex:String( number:Int )
	Return base_convert( String( number ), 10, 16 )
End

'Overloading Functions
Function dechex:String( number:String )
	Return base_convert( number , 10, 16 )
End

Function dec2hex:String( number:String )
	Return base_convert( number , 10, 16 )
End


#Rem
	Returns the decimal equivalent of the hexadecimal number represented
	by the hex_string argument. hexdec() converts a hexadecimal string to a decimal number.

	hexdec() will ignore any non-hexadecimal characters it encounters.
	
	TODO:		Simplify the code ( I think it's do-able )
	Author:	Nicholas Grant
	@updatedby	
	@return	int
#End
Function hexdec:Int( hex_string:String )
	
	Local hstr:String = ""
	Local nord:Int = 0
	
	hex_string = hex_string.ToUpper()
	
	For Local cnt:Int=0 To hex_string.Length
		nord = hex_string[cnt]
		If ( ( nord > 47 And nord < 58 ) Or (nord > 64 And nord < 71 ) )
			hstr += hex_string[cnt..cnt+1]
		Endif
	Next
	
	Return Int( base_convert( hstr, 16, 10 ) )
End

' Alias function
Function hex2dec:Int( hex_string:String )
	Return hexdec( hex_string )
End


#Rem
	Summary: Returns base raised to the power of exp.
	
	Author:	Nicholas Grant
	@bugfix	Fixed output value
	@updatedby	
	@return	int
#End
Function pow:Int( base:Int, expo:Int )
	Local output:Int = 1
	For Local cnt:Int=0 To expo - 1
		output *= base
	Next
	Return output
End



' #################### string ####################

' Monkey can't seem to handle being stacked in folders
' So use the search button to find the functions if you
' want to modify them.


#Rem
	Summary: Returns an ASCII string containing the hexadecimal representation of str.
	The conversion is done byte-wise with the high-nibble first. 
	
	Author:	Nicholas Grant
	@updatedby	
	@return	string
#End
Function bin2hex:String( str:String )
	Return base_convert( str, 2, 16 )
End


#Rem
	Summary: Alias of rtrim()
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	str
	@param	string	charlist = ""
	
	@return	string
#End
Function chop:String( str:String, charlist:String = "" )
	Return rtrim( str, charlist )
End


#Rem
	Summary: Return a specific character
	
	Author:	Nicholas Grant
	@updatedby	
	
	@return	string
#End
Function chr:String( ascii:Int )
	Return String.FromChar( ascii )
End


#Rem
	Returns an array of strings, each of which is a substring of string formed by
	splitting it on boundaries formed by the string delimiter. 
	
	Author:	Nicholas Grant
	@updatedby	
	@param	string	delimiter
	@param	string	str
	@param	int		limit = 0
	@variable	string[]	splitStringSplits
	@variable	string[]	returnStringArray
	@return	string array
#End
Function explode:String[] ( delimiter:String, str:String, limit:Int = 0 )
	
	Local splitStringSplits:String[] = str.Split( delimiter )	
	Local returnStringArray:String[]
	
	If ( Not limit )
		returnStringArray = splitStringSplits
	Else
		
		returnStringArray = New String[limit]
		For Local cnt:Int = 0 To limit - 1
			
			If ( cnt < limit - 1 )
				returnStringArray[cnt] = splitStringSplits[cnt]
			Else
				
				For Local cnt2:Int = cnt To splitStringSplits.Length - 1
					
					If ( cnt2 < splitStringSplits.Length - 1 )
						returnStringArray[cnt] += splitStringSplits[cnt2] + delimiter
					Else
						returnStringArray[cnt] += splitStringSplits[cnt2]
					End If
					
				Next
				
			End If
			
		Next
		
	End If
	
	Return returnStringArray
	
End


#Rem
	Summary: Strip whitespace (or other characters) from the beginning of a string.
	Author:	Nicholas Grant
	
	@param	string	str
	@param	string	charlist = ""
	
	@variable	string[]	excludedCharlistSplits
	@variable	integer	notExcludedCharCount
	@variable	integer	cnt
	@variable	integer	stringStartPosition
	
	@return	string
#End
Function ltrim:String( str:String, charlist:String = "" )
	
	Local excludedCharlistSplits:String[]
	Local notExcludedCharCount:Int = 0
	Local cnt:Int = 0
	Local cnt2:Int = 0
	Local stringStartPosition:Int = 0
	
	If ( charlist = "" )
		charlist = " ~t~n~r~0" + chr(11)
	End If
	
	excludedCharlistSplits = str_split( charlist )
	
	While ( cnt < str.Length - 1 )
		
		For cnt2 = 0  To excludedCharlistSplits.Length - 1
			
			If ( str[cnt..cnt+1][0] = excludedCharlistSplits[cnt2][0] )
				stringStartPosition += 1
			Else
				notExcludedCharCount += 1
			End If
			
		Next
		
		cnt += 1
		If ( notExcludedCharCount = excludedCharlistSplits.Length )
			cnt = str.Length
		End If
		notExcludedCharCount = 0
		
	Wend
	
	str = str[stringStartPosition..str.Length]
	
	Return str
End


#Rem
	Summary: Returns the ASCII value of the first character of string.
	
	Author:	Nicholas Grant
	@updatedby	
	@param	string	char
	@return	int
#End
Function ord:Int(char:String)
	Return char[0]
End


#Rem
	Summary: Strip whitespace (or other characters) from the end of a string
	
	NOTE:		I tried to optimize the speed of this function ( notice the use of substr only once )
			Other than that, cnt2 was also initialized once instead of every pass through the for loop
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	str
	@param	string	charlist = ""
	
	@variable	string[]	excludedCharlistSplits
	@variable	integer	notExcludedCharCount
	@variable	integer	cnt
	@variable	integer	cnt2
	@variable	integer	stringLength
	
	@return	string
#End
Function rtrim:String( str:String, charlist:String = "" )
	
	Local excludedCharlistSplits:String[]
	Local notExcludedCharCount:Int = 0
	Local cnt:Int = 0
	Local cnt2:Int = 0
	Local stringLength:Int = str.Length
	
	If ( charlist = "" )
		charlist = " ~t~n~r~0" + chr(11)
	End If
	
	excludedCharlistSplits = str_split( charlist )
	cnt = str.Length
	While ( cnt )
		
		For cnt2 = 0 To excludedCharlistSplits.Length - 1
			
			If ( str[cnt-1..cnt][0] = excludedCharlistSplits[cnt2][0] )
				stringLength -= 1
			Else
				notExcludedCharCount += 1
			End If
			
		Next
		
		cnt -= 1
		
		If ( notExcludedCharCount = excludedCharlistSplits.Length )
			cnt = 0
		End If
		
		notExcludedCharCount = 0
		
	Wend
	str = substr( str, 0, stringLength )
	Return str
	
End


Const STR_PAD_LEFT:Int = 0
Const STR_PAD_RIGHT:Int = 1
Const STR_PAD_BOTH:Int = 2

#Rem
	This functions returns the input string padded on the left, the right, or both sides
	to the specified padding length. If the optional argument pad_string is not supplied,
	the input is padded with spaces, otherwise it is padded with characters from pad_string
	up to the limit.
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	strinput
	@param	integer	pad_length
	@param	string	pad_string = " "
	@param	integer	pad_type = STR_PAD_RIGHT
	
	@variable	string	stringOutput
	@variable	integer	stringIndexMax
	@variable	integer	padding
	@variable	integer	padRight
	@variable	integer	cnt
	
	@return	string
#End
Function str_pad:String(strinput:String, pad_length:Int, pad_string:String = " ", pad_type:Int = STR_PAD_RIGHT)
	
	Local stringOutput:String = ""
	Local stringIndexMax:Int = pad_string.Length
	
	If ( strinput.Length < pad_length )
		
		Local padding:Int = pad_length - strinput.Length
		Local padRight:Int = 0
		Local cnt:Int = 0
		
		Select ( pad_type )
			
			Case 0
				
				For cnt = 0 To padding - 1
					stringOutput += substr( pad_string, cnt Mod stringIndexMax, 1 )
				Next
				
				stringOutput += strinput
				
			Case 1
				
				stringOutput += strinput
				padRight = stringOutput.Length
				
				For cnt = padRight To pad_length - 1
					stringOutput += substr( pad_string, cnt Mod stringIndexMax, 1 )
				Next
				
			Case 2
				
				For cnt = 0 To Floor( ( padding - 2 ) / 2.0 )
					stringOutput += substr( pad_string, cnt Mod stringIndexMax, 1 )
				Next
				
				stringOutput += strinput
				padRight = stringOutput.Length
				
				For cnt = padRight To pad_length - 1
					stringOutput += substr( pad_string, cnt Mod stringIndexMax, 1 )
				Next
				
		End Select
		
	Else
		stringOutput = strinput
	End If
	
	Return stringOutput
	
End


#Rem
	Summary: Returns strinput repeated multiplier times.
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	strinput
	@param	integer	multiplier
	
	@variable	string	stringOutput
	@variable	integer	cnt
	
	@return	string
#End
Function str_repeat:String( strinput:String, multiplier:Int )
	
	Local stringOutput:String = ""
	
	For Local cnt:Int = 1 To multiplier
		stringOutput += strinput
	Next
	
	Return stringOutput
	
End


#Rem
	This function returns a string or an array with all occurrences of
	strsearch in strsubject replaced with the given strreplace value. 
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	strseach
	@param	string	strreplace
	@param	string	strsubject
	
	@return	string
#End
Function str_replace:String( strsearch:String, strreplace:String, strsubject:String )
	Return strsubject.Replace( strsearch, strreplace )
End


#Rem
	Summary: Performs the ROT13 encoding on the str argument and returns the resulting string.
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	str
	
	@variable	string	stringOutput
	@variable	integer	stringLength
	@variable	integer	currentCharValue
	@variable	integer	cnt
	
	@return	string
#End
Function str_rot13:String(str:String)
	
	Local stringOutput:String = ""
	Local stringLength:Int = str.Length
	Local currentCharValue:Int = 0
	
	If ( stringLength > 0 )
		
		For Local cnt:Int = 0 To ( stringLength - 1 )
			
			currentCharValue = ord( substr( str, cnt, 1 ) )
			
			If ( currentCharValue > 96 And currentCharValue < 123 )
				stringOutput += chr( ( ( currentCharValue - 84 ) Mod 26 ) + 97 )
			Elseif ( currentCharValue > 64 And currentCharValue < 91 )
				stringOutput += chr( ( currentCharValue Mod 26 ) + 65 )
			Else
				stringOutput += substr( str, cnt, 1 )
			Endif
			
		Next
		
	Endif
	
	Return stringOutput
	
End


#Rem
	Summary: Converts a string to an array.
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	str
	@param	integer	split_length = 1
	
	@variable	string	returnStringArray
	@variable	 List	splitStringList
	@variable	integer	numberOfStringSplits
	@variable	integer	currentStringIndex
	@variable	integer	splitStringListLength
	@variable	integer	cnt
	
	@return	string array
#End
Function str_split:String[] (str:String, split_length:Int = 1)
	
	If (str.Length > 0)
		
		Local splitStringList:StringList = New StringList
		Local numberOfStringSplits:Int = Int(str.Length / split_length)
		Local currentStringIndex:Int = 0
		
		While ( currentStringIndex <= numberOfStringSplits )
			
			If ( ( currentStringIndex * split_length ) + split_length < str.Length )
				splitStringList.AddLast( substr( str, currentStringIndex * split_length, split_length ) )
			Else
				splitStringList.AddLast( str[currentStringIndex*split_length..str.Length] )
			End If
			
			currentStringIndex += 1
			
		Wend
		
		Return splitStringList.ToArray()
		
	Endif
	
	Return [""]
	
End


#Rem
	Summary: Return information about words used in a string.
	
	Author:	Nicholas Grant
	@updatedby	
	@return	int
	@return	string array
#End
Function str_word_count:Int(str:String)
	
	If (str.Length > 0)
		
		Local splitLetters:String[] = str_split(str)
		Local currentLetterValue:Int = 0
		Local splitLettersLength:Int = splitLetters.Length
		Local newWord:String = ""
		Local splitWordsList:StringList = New StringList
		
		For Local cnt:Int = 0 To splitLettersLength - 1
			currentLetterValue = ord(splitLetters[cnt])
			If ((currentLetterValue > 64 And currentLetterValue < 91) Or (currentLetterValue > 96 And currentLetterValue < 123))
				newWord += splitLetters[cnt]
			Else
				If (newWord <> "") Then splitWordsList.AddLast(newWord)
				newWord = ""
			End
		Next
		Return splitWordsList.Count()
		
	End
	
	Return 0
	
End

Function str_word_count:String[] (str:String, format:Int, charlist:String = "")
	Local splitWords:String[]
	If (str.Length > 0)
		Local splitLetters:String[] = str_split(str)
		Local newWord:String = ""
		Local currentLetterValue:Int = 0
		Local splitLettersLength:Int = splitLetters.Length - 1
		Local skipToNext:Int = False
		Local splitWordsList:StringList = New StringList
		
		Select (format)
			Case 1
				For Local cnt:Int = 0 To splitLettersLength
					currentLetterValue = ord(splitLetters[cnt])
					If ((currentLetterValue > 64 And currentLetterValue < 91) Or (currentLetterValue > 96 And currentLetterValue < 123))
						newWord+=splitLetters[cnt]
					Else
						If (newWord <> "") Then splitWordsList.AddLast(newWord)
						newWord = ""
					End
				Next
			Case 2
				Local charlistValue:Int[] = New Int[charlist.Length]
				For Local cnt:Int = 0 To charlist.Length - 1
					charlistValue[cnt] = ord( substr(charlist, cnt, 1) )
				Next
				For Local cnt:Int = 0 To splitLettersLength
					currentLetterValue = ord(splitLetters[cnt])
					If ((currentLetterValue > 64 And currentLetterValue < 91) Or (currentLetterValue > 96 And currentLetterValue < 123))
						newWord+=splitLetters[cnt]
					Else
						For Local cnt2:Int = 0 To charlistValue.Length - 1
							If (currentLetterValue = charlistValue[cnt2] And Not skipToNext)
								newWord+=splitLetters[cnt]
								skipToNext = True
							End
						Next
						If (Not skipToNext)
							If (newWord <> "") Then splitWordsList.AddLast(newWord)
							newWord = ""
						End
					End
					skipToNext = False
				Next
		End
		If (newWord <> "") Then splitWordsList.AddLast(newWord)
		Return splitWordsList.ToArray()
	End
	Return [""]
End


#Rem
	Summary: Returns the length of the given string.
	
	Author:	Nicholas Grant
	@updatedby	
	@return	int
#End
Function strlen:Int(str:String)
	Return str.Length
End


#Rem
	Summary: Returns the portion of string specified by the start and length parameters.
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	str
	@param	integer	start
	@param	integer	length
	
	@return	string
	@return	string containing null
#End
Function substr:String( str:String, start:Int, length:Int = 0)
	If ( start < 0 )
		start = str.Length + start
	End If
	If ( Not length )
		length = str.Length - start
	End If
	If ( length < 0 )
		length = str.Length + length - start
		If ( ( start + length ) > str.Length )
			length = str.Length - start
		End If
	End If
	If ( str.Length < start Or start < 0 Or ( start + length ) <= start  )
		Return "~z"
		' Returns false (0), but, this is Monkey, don't want to return wrong type
		' Use empty() to determine if the string returns null
	End If
	
	Return str[start..(start + length)]
End


#Rem
	Summary: Strip whitespace (or other characters) from the beginning and end of a string.
	
	Author:	Nicholas Grant
	@updatedby	
	
	@param	string	str
	@param	string	charlist = ""
	
	@return	string
#End
Function trim:String( str:String, charlist:String = "" )
	Return ltrim( rtrim( str, charlist ), charlist )
End


' #################### variableHandling ####################

#Rem
	Summary: Determine whether a variable (string for main) is considered to be empty.
	This ignores "0" in favor of "~z" which is null, or Chr(0), if you want.
	
	Author:	Nicholas Grant
	@updatedby	
	@return	boolean
#End
Function empty:Int( str:String )
	If ( str.Length <= 1 )
		Local notChars:String[] = [ "", "~z" ]
		
		For Local cnt:Int = 0 To notChars.Length - 1
			If ( str = notChars[cnt] )
				Return True
			End
		Next
	End
	Return False
End

Function empty:Int( val:Int )
	If ( val <> 0 )
		Return False
	End
	Return True
End

Function empty:Int( val:Float )
	If ( val <> 0 Or val <> 0.0 )
		Return False
	End
	Return True
End

